热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

外界|求是_Android当中的MVP模式插曲封装OkHttp

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android当中的MVP模式插曲-封装OkHttp相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android当中的MVP模式插曲-封装OkHttp相关的知识,希望对你有一定的参考价值。




个人博客:CODE FRAMER BIGZ


MVP系列文章配套DEMO


Android 当中的 MVP 模式(一)基本概念



Android 当中的 MVP 模式(二)封装



Android 当中的 MVP 模式(三)基于分页列表的封装



Android 当中的 MVP 模式(四)插曲-封装 OkHttp



Android 当中的 MVP 模式(五)封装之后的 OkHttp 工具在 Model 层的使用



Android 当中的 MVP 模式(六)View 层 Activity 的基类— BaseMvpActivity 的封装



Android 当中的 MVP 模式(七)终篇—关于对 MVP 模式中代码臃肿问题的思考


摘要:前两篇中使用的网络请求工具是 OkHttp ,并没有经过封装,都是简单的使用 get 请求,并且将错误全部都抛到上层去解决了, 这无形之中增加了上层的编码复杂度,即使要抛向上层,起码也要给一个 errorCode 或者是 errorMsg 吧,所以这边文章就针对 OkHttp 进行封装,然后将封装之后的工具使用到上一小结的 Demo 之中。


官方给的例子


  • 同步方法

    OkHttpClient client = new OkHttpClient();
    String run(String url) throws IOException
    Request request = new Request.Builder()
    .url(url)
    .build();
    Response response = client.newCall(request).execute();
    return response.body().string();

  • 异步方法

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
    .url(url)
    .build();
    client.newCall(request).enqueue(new Callback()
    @Override
    public void onFailure(Call call, IOException e)

    @Override
    public void onResponse(Call call, Response response) throws IOException


对异步方法的分析

结合上面异步方法,稍作分析,涉及到如下几个对象 OkHttpClient , Request , Call,Response ,其他的都一些方法的调用,所以我们的封装应该重点针对这三个对象来进行。


Request

RequestOkhttp 当中是抽象出来的一个请求对象,它封装了请求报文信息:请求的 Url 地址,请求的方法(Get Post等),各种请求头(Content-Type COOKIE)以及可以选择的请求体,一般通过内部的 Builder 类来构建对象,建筑者设计模式。

那么我们这里就针对 Post Get 两种请求方式做封装,但是这里又涉及到一个问题,就是我们还需要参数,用于拼接请求 Url 的参数,举个栗子:

这是搜狐电视剧频道的 API 接口:

http://api.tv.sohu.com/v4/search/channel.json%22%20+%20%22?cid=2&o=1&plat=6&poid=1&api_key=9854b2afa779e1a6bff1962447a09dbd&%22%20+%20%22sver=6.2.0&sysver=4.4.2&partner=47&page=1&page_size=10

这么看可能特别的麻烦,我们把它拆分一下:

String baseUrl=http://api.tv.sohu.com/v4/search/channel.json%22%20+%20%22

然后剩下的都是参数了,以键值对的形式存在:

这些参数拼接在 baseUrl 后面的顺序是没有要求的,不一定要按照上面的顺序来,只要每个参数都按照固定的格式出现就可以

看上面的完整 Url 可以发现规律,在 baseUrl 后面有一个 ?, 然后就就是 key1=value1&key2=value2&key3=value3 这种形式的

其实遵循 RESTful API 设计的接口,都会是这种形式,所以这里也利于我们进行封装了。而 key-value 这种形式,就特别适合使用 Map 结构来封装。

说这么多,上代码,首先是对参数进行封装:

RequestParam

/**
* @author:fanyuzeng
* @date: 2017/10/27 13:55
* @desc: 封装url中的参数
*/
public class RequestParams
/**
* 使用@link ConcurrentHashMap是为了保证线程安全
*/
private ConcurrentHashMap urlParams &#61; new ConcurrentHashMap<>();
public RequestParams()

public RequestParams(Map source)
for (Map.Entry entry : source.entrySet())
put(entry.getKey(), entry.getValue());


public RequestParams(String key, String value)
put(key, value);

private void put(String key, String value)
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value))
urlParams.put(key, value);


public ConcurrentHashMap getUrlParams()
return urlParams;

这地方使用 ConcurrentHashMap 就是为了保证线程安全的&#xff0c;这个类使用的是锁分段技术&#xff0c;不同于一般的同步方法或者是同步代码块&#xff0c;它只会锁住其中一个 segment&#xff0c;其他的 segment 仍然是可以访问的&#xff0c;所以他的效率会比 synchronized 高。

有了 RequestParam 之后&#xff0c;就可以使用它来拼接 url&#xff0c;有了 url 之后&#xff0c;就可以使用它来构建 Request对象了。

CommonRequest

1 /**
2 * &#64;author: fanyuzeng
3 * &#64;date: 2017/10/27 14:08
4 * &#64;desc: response for build various kind of &#64;link okhttp3.Request include Get Post upload etc.
5 */
6 public class CommonRequest
7 private static final String TAG &#61; "CommonRequest";
8 /**
9 * create a Get request
10 *
11 * &#64;param baseUrl base url
12 * &#64;param params see &#64;link RequestParams
13 * &#64;return &#64;link Request
14 * &#64;created at 2017/10/27 14:39
15 */
16 public static Request createGetRequest(&#64;NonNull String baseUrl, &#64;Nullable RequestParams params)
17 StringBuilder urlBuilder &#61; new StringBuilder(baseUrl).append("?");
18 if (params !&#61; null)
19 //将请求参数合并进url中
20 for (Map.Entry entry : params.getUrlParams().entrySet())
21 urlBuilder.append(entry.getKey()).append("&#61;").append(entry.getValue()).append("&");
22
23
24 Log.d(TAG,">> createGetRequest >> " &#43; urlBuilder.toString());
25
26 return new Request.Builder().get().url(urlBuilder.substring(0, urlBuilder.length() - 1)).build();
27
28
29 /**
30 * create a post request
31 *
32 * &#64;param baseUrl base url
33 * &#64;param params see &#64;link RequestParams
34 * &#64;return &#64;link Request
35 * &#64;created at 2017/10/27 14:39
36 */
37 public static Request createPostRequest(&#64;NonNull String baseUrl, &#64;NonNull RequestParams params)
38 FormBody.Builder mFormBodyBuilder &#61; new FormBody.Builder();
39 for (Map.Entry entry : params.getUrlParams().entrySet())
40 mFormBodyBuilder.add(entry.getKey(), entry.getValue());
41
42 FormBody formBody &#61; mFormBodyBuilder.build();
43 return new Request.Builder().post(formBody).url(baseUrl).build();
44
45
46

16 行的 createGetRequest 方法是用于创建一个 Get 请求&#xff0c;主要就是使用 StringBuilder 进行 Url 的拼接&#xff0c;第 37 行的 createPostRequest 方法是用于创建一个 Post 请求的。 Post 请求是先创建 FormBody &#xff0c;然后和 baseUrl 一个构造 Request

封装到这里&#xff0c; Request 就算是封装完了&#xff0c; 当然这里只封装了 Post Get &#xff0c;也可以继续封装文件上传和文件下载的Request。


Call

Call 代表的是一个实际的 HTTP 请求&#xff0c;它是链接 RequestResponse 的桥梁&#xff0c;通过 Request 对象的 newCall 方法可以得到一个 Call 对象&#xff0c;既支持同步获取数据&#xff0c;也支持异步&#xff0c;在上面官方例子里&#xff0c;也可以看出来&#xff0c;在异步回调中&#xff0c;当获取到数据&#xff0c;会将 Response 对象传入 CallbackonSuccess 方法中&#xff0c;如果请求没有成功&#xff0c;就会调用 onFailure 方法&#xff08;Response 下面说&#xff09;。那么看看 Callback 是什么。

先看看官方的 Callback 是什么 &#xff1a;

public interface Callback
void onFailure(Call call, IOException e);
void onResponse(Call call, Response response) throws IOException;

对&#xff0c;把注释删除了之后&#xff0c;其实就是两个接口&#xff0c;简单的理解成&#xff0c;一个是请求成功时的回调&#xff0c;一个是请求失败时的回调。

那么对这一层的封装思路是这样子的&#xff1a;

一般来说&#xff0c;在上层&#xff0c;我们是需要去处理上面两个回调的&#xff0c;在 onFailure 中&#xff0c;请求失败&#xff0c;应该做什么操作&#xff0c;在 onResponse 中&#xff0c;HTTP 返回的状态码在 [200,300&#xff09;之间应该有什么操作&#xff0c;在其他区间又应该有什么操作。那么在这里&#xff0c;我们就创建一个类&#xff0c;去实现这个接口&#xff0c;将基本的处理都在这个类里写好&#xff0c;出错误了&#xff0c;就拿到 erroeCode errorMsg 回调给上层&#xff0c;正确的返回信息&#xff0c;就直接回调给上一层

那么这里就涉及到我们自定义的一个 ExceptionListener 以及实现了 Callback 接口的 CommonCallback 类。

OkHttpException

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 13:44
* &#64;desc:
*/
public class OkHttpException extends Exception
private int mErrorCode;
private String mErrorMsg;
public OkHttpException(int errorCode, String errorMsg)
this.mErrorCode &#61; errorCode;
this.mErrorMsg &#61; errorMsg;

public int getErrorCode()
return mErrorCode;

public String getErrorMsg()
return mErrorMsg;

DisposeDataListener

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 13:49
* &#64;desc:
*/
public interface DisposeDataListener
/**
* 请求服务器数据成功时回调的方法
*
* &#64;param responseObj 需要回调到上层的请求结果
*/
void onSuccess(Object responseObj);
/**
* 请求服务器失败时候的回调方法
*
* &#64;param exception 需要回调到上层的错误反馈
*/
void onFailure(OkHttpException exception);

再将这个 Listener代理设计模式再封装一层

DisposeDataHandler

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 13:52
* &#64;desc: 代理模式,使用DisposeDataHandler 代理 DisposeDataListener的操作
*/
public class DisposeDataHandler
public DisposeDataListener mListener;
public Class mClass;
public DisposeDataHandler(DisposeDataListener listener)
mListener &#61; listener;

public DisposeDataHandler(DisposeDataListener listener, Class aClass)
mListener &#61; listener;
mClass &#61; aClass;

public void onSuccess(Object responseObj)
mListener.onSuccess(responseObj);

public void onFailure(OkHttpException exception)
mListener.onFailure(exception);

public Class getClassType()
return mClass;


此处用代理模式&#xff0c;主要是为了优雅&#xff08;装X&#xff09;的处理 Class 这个对象&#xff0c;这是用于映射的类型&#xff0c;在调用 Listener 的回到方法之后做判断这个对象是否存在&#xff0c;是&#xff0c;则再映射在返回&#xff0c;否&#xff0c;直接返回。

然后将三面三个类聚合到一起

CommonJsonCallback

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 14:41
* &#64;desc:
*/
public class CommonJsonCallback implements Callback
private static final String TAG &#61; "CommonJsonCallback";
private static final String MSG_RESULT_EMPTY &#61; "request could not be ececuted";
private static final String MSG_JSON_EMPTY &#61; "json is empty or null";
private static final String MSG_RETURN_CODE &#61; "http return code is not [200,300)";
private static final int NETWORK_ERROR &#61; -1;
private static final int JSON_ERROR &#61; -2;
private Handler mDeliveryHandler &#61; new Handler(Looper.getMainLooper());
private Gson mGson &#61; new Gson();
private DisposeDataHandler mDisposeDataHandler;
public CommonJsonCallback(DisposeDataHandler dataHandler)
mDisposeDataHandler &#61; dataHandler;

&#64;Override
public void onFailure(&#64;NonNull Call call, &#64;NonNull final IOException e)
mDeliveryHandler.post(new Runnable()
&#64;Override
public void run()
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_RESULT_EMPTY &#43; e.getMessage()));

);

&#64;Override
public void onResponse(&#64;NonNull Call call, &#64;NonNull final Response response) throws IOException
if (!response.isSuccessful())
mDeliveryHandler.post(new Runnable()
&#64;Override
public void run()
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_RETURN_CODE &#43; response.message()));

);

final String resultJson &#61; response.body().string();
mDeliveryHandler.post(new Runnable()
&#64;Override
public void run()
handleResponse(resultJson);

);

private void handleResponse(String resultJson)
if (TextUtils.isEmpty(resultJson))
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_JSON_EMPTY));
return;

if (mDisposeDataHandler.getClassType() &#61;&#61; null)
mDisposeDataHandler.onSuccess(resultJson);
else
Object mappedDataType &#61; mGson.fromJson(resultJson, mDisposeDataHandler.getClassType());
if (mappedDataType &#61;&#61; null)
mDisposeDataHandler.onFailure(new OkHttpException(JSON_ERROR, MSG_JSON_EMPTY));
else
mDisposeDataHandler.onSuccess(mappedDataType);



自我感觉用代理之后&#xff0c;处理对象都是 DisposeHandler &#xff0c;不会在看到 Listener Class ,适应起来方便些了。

要注意一点是&#xff0c;在 onResponse 方法中&#xff0c;还是在子线程中的&#xff0c;要及时切换线程。

到这里&#xff0c;就对 Call 这个对象封装完成了。


Response

Response 类封装了响应报文信息&#xff1a;状态吗&#xff08;200404 等&#xff09;、响应头&#xff08;Content-TypeServer 等&#xff09;以及可选的响应体。可以通过 Call 对象的 execute() 方法获得 Response 对象&#xff0c;异步回调执行 Callback 对象的 onResponse 方法时也可以获取 Response 对象。

这东西人家已经给我们封装好了&#xff0c; 需要什么直接去拿就行&#xff0c; 也不需要在封装。


OkHttpClient

官方文档有这么一句话&#xff1a;



OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.


翻译一下&#xff1a;当你使用一个全局的 OkHttpClient &#xff0c;并且重用它发起 HTTP 请求的时候&#xff0c;OkHttp 的能够发挥最 NB 的性能&#xff0c;因为每一个客户端都持有它的连接池和线程池&#xff0c;如果这俩东西可以重用的话&#xff0c;那么就能减少潜在的因素&#xff0c;并且节省内存&#xff0c;相反的&#xff0c;如果为每一个客户端的每一个请求都创建一个 OkHttpClient &#xff0c;那么就会浪费空闲的连接池和线程池中的资源。

叽叽歪歪这么多&#xff0c;就是说用 OkHttpClient 的时候要用单例模式

刚开始我是这么设计的&#xff1a;

CommonokHttpClient

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 15:21
* &#64;desc:
*/
&#64;Deprecated
public class CommonOkHttpClient
private static final int TIME_OUT &#61; 30;
private static OkHttpClient sOkHttpClient;
static
OkHttpClient.Builder builder &#61; new OkHttpClient.Builder();
builder.hostnameVerifier(new HostnameVerifier()
&#64;Override
public boolean verify(String hostname, SSLSession session)
return true;

);
builder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
builder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
builder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
//允许重定向
builder.followRedirects(true);
// TODO: 2017/10/27 https
sOkHttpClient &#61; builder.build();

/**
* 请求服务器数据的方法
*
* &#64;param request Use &#64;link com.project.fanyuzeng.mvpdemo.utils.okhttp.request.CommonRequest to build
* &#64;param handler see &#64;link DisposeDataHandler proxy class
*/
public static void requestServerData(Request request, DisposeDataHandler handler)
sOkHttpClient.newCall(request).enqueue(new CommonJsonCallback(handler));

恩&#xff0c;静态代码块中初始化实例化 OkHttpClient&#xff0c;我认为饿汉模式没有本质的区别&#xff0c; 但是这种方式比饿汗模式的初始化时间更早。



好吧 &#xff0c;我承认我懒&#xff0c;不想在整个单例类出来。。


这样写&#xff0c;也没什么问题&#xff0c;但是外界在使用的使用&#xff0c;比较麻烦


  1. 创建RequestParams&#xff0c;涉及到 HashMap 的好多 put 操作
  2. RequestParam 去初始化 CommonRequest
  3. 在上层根据请求方式去创建对应的 Request
  4. 再实例化一个DisposeHandler

所以只好接着封装吧&#xff0c;分析上面 4 个步骤&#xff0c;其中步骤 1 那是不能再简化了的&#xff0c;因为具体的请求参数肯定是要从外界传进来的&#xff0c;这里涉及到的 HashMap 以及它的 put 操作是不可避免的。步骤 2 和步骤 3 完全是可以封装一下的&#xff0c;步骤 4 也是需要从外外界回调的方法&#xff0c;类似于点击监听的 onClick 方法回调。

所以把 CommonOkHttpClientDeprecated 掉&#xff0c;重新来一个

OkHttpManager

/**
* &#64;author:fanyuzeng
* &#64;date: 2017/10/27 17:57
* &#64;desc:
*/
public class OkHttpManager
private static volatile OkHttpManager sManager;
private OkHttpClient mOkHttpClient;
private OkHttpManager()
OkHttpClient.Builder builder &#61; new OkHttpClient.Builder();
builder.hostnameVerifier(new HostnameVerifier()
&#64;Override
public boolean verify(String hostname, SSLSession session)
return true;

);
builder.connectTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
builder.readTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
builder.writeTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
//允许重定向
builder.followRedirects(true);
// TODO: 2017/10/27 https
mOkHttpClient &#61; builder.build();

public static OkHttpManager getInstance()
if (sManager &#61;&#61; null)
synchronized (OkHttpManager.class)
if (sManager &#61;&#61; null)
sManager &#61; new OkHttpManager();



return sManager;

/**
* 使用&#64;link OkHttpClient想服务器端请求数据的方法
* &#64;param method &#64;link Constants#HTTP_GET_METHOD Get方式,&#64;link Constants#HTTP_POST_METHOD Post方式
* &#64;param baseUrl baseUrl
* &#64;param paramsMap 请求url的参数,以键值对的形式存放
* &#64;param handler
*/
public void requestServerData(int method, String baseUrl, HashMap paramsMap, DisposeDataHandler handler)
RequestParams requestParams &#61; new RequestParams(paramsMap);
Request request &#61; null;
if (method &#61;&#61; Constants.HTTP_GET_METHOD)
request &#61; CommonRequest.createGetRequest(baseUrl, requestParams);
else if (method &#61;&#61; Constants.HTTP_POST_METHOD)
request &#61; CommonRequest.createPostRequest(baseUrl, requestParams);

if (request !&#61; null)
mOkHttpClient.newCall(request).enqueue(new CommonJsonCallback(handler));




好吧&#xff0c;还是用双重锁模式的单例比较放心 。


到此就封装完了&#xff0c;下面简单的使用一下。


使用姿势

1 OkHttpManager.getInstance().requestServerData(method, url, mPaginationPresenter.getParams(), new DisposeDataHandler(new DisposeDataListener()
2 &#64;Override
3 public void onSuccess(Object responseObj)
4 String responseJson &#61; (String) responseObj;
5 Log.d(TAG, ">> onSuccess >> " &#43; responseJson);
6 mPaginationPresenter.accessSuccess(responseJson);
7
8 &#64;Override
9 public void onFailure(OkHttpException exception)
10 Log.d(TAG, ">> onFailure >> " &#43; exception.getErrorCode());
11 mPaginationPresenter.okHttpError(exception.getErrorCode(), exception.getErrorMsg(), url);
12
13 ,null));

  • 没有将 Json 数据映射成实体类&#xff0c; 所以在 13 行构造 DisposeDataHandler 的时候&#xff0c;第二个 类参数传的是 null
  • 这个例子是结合上一篇请求分页数据来用的&#xff0c;所以这里直接将 Json 数据抛给 Presenter 层&#xff0c;让它去处理。
  • 1 行的 mPaginationPresenter.getParams() 就是拿 url 中的参数。

由于篇幅的限制&#xff0c;这一篇先到这里&#xff0c;下一篇再把这个封装的 OkHttp 工具用于分页数据的请求。

最后&#xff0c;贴个 AS 中封装之后工具的结构图。

个人博客地址 &#xff1a;CODER FRAMER BIGZ


推荐阅读
  • 目前正在做毕业设计,一个关于校园服务的app,我会抽取已完成的相关代码写到文章里。一是为了造福这个曾经帮助过我的社区,二是写文章的同时更能巩固相关知识的记忆。一、前言在爬取教务系统 ... [详细]
  • 对okhttp网络请求的简单介绍publicclassAppextendsApplication{OkHttpClient实例是唯一的,所有的请求都会通过 ... [详细]
  • android之OkHttpClient通信OkHttpClient用法1:自定义缓存OkHttpClienthttpclientnewOkHttpClient.Builder() ... [详细]
  • 注意:以下分析都是基于Retrofit2转载请注明出处:http:blog.csdn.netevan_manarticledetails51320637本节是《Retrofit的使 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 09 性能优化网络优化
    如何优化一个网络请求呢?相信大家在面试的时候可能会被问到这个问题。今天我其实就是讲述下我知道的一些简单的优化方式,可以帮助大家在面试的过程中得到点基础分数。 ... [详细]
  • 美团Android 岗3次挂了,这次终于成功拿下!
    美团Android岗3次挂了,这次终于成功拿下!-面试流程自我介绍回答问题————(详情看下面的攻略)前面会问你很多技术问题,从简单到难,直到问到你打不出来就会又问其他部分的,也是 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • AFNetwork框架(零)使用NSURLSession进行网络请求
    本文介绍了AFNetwork框架中使用NSURLSession进行网络请求的方法,包括NSURLSession的配置、请求的创建和执行等步骤。同时还介绍了NSURLSessionDelegate和NSURLSessionConfiguration的相关内容。通过本文可以了解到AFNetwork框架中使用NSURLSession进行网络请求的基本流程和注意事项。 ... [详细]
  • 毕业设计做的项目,答辩完了,就共享出来。波尼音乐是一款开源Android在线音乐播放器。 ... [详细]
author-avatar
美丽凍人2502938087
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有